use crate::error::SysError;
use crate::file_system::write_to_file;
use crate::model::Composition;
use crate::model::{EntryInfo, NpmModuleInfo, ProjectDirInfo, SlotInfo, VueBlock};
use itertools::Itertools;
use std::fs;

pub fn init_template(
    project_dir_info: &ProjectDirInfo,
    npm_modules: &[NpmModuleInfo],
    composition: &Composition,
) -> Result<(), Box<dyn std::error::Error>> {
    if project_dir_info.app_vue().exists() {
        let base_entry: EntryInfo = find_module(npm_modules, &composition.base_name)
            .and_then(|module| module.try_get_entry_info())
            .ok_or_else(|| SysError::msg(&format!("未定义base:{}", &composition.base_name)))?;

        let slot_infos: Vec<SlotInfo> = composition
            .composing_methods
            .clone()
            .into_iter()
            .map(|composing_method| {
                if composing_method.len() != 2 {
                    return Err(SysError::msg("composing_method 长度应等于2"));
                }
                let slot_name = &composing_method[0];
                let element_package_name = &composing_method[1];
                let element_entry = find_module(npm_modules, element_package_name)
                    .and_then(|module| module.try_get_entry_info())
                    .ok_or_else(|| {
                        SysError::msg(&format!(
                            "未定义element_entry_name:{}",
                            element_package_name
                        ))
                    })?;
                Ok(SlotInfo {
                    slot_name: slot_name.clone(),
                    element_name: element_entry.name,
                })
            })
            .try_collect()?;

        let template_content = build_template(&base_entry, &slot_infos);

        write_to_file(
            project_dir_info.app_vue(),
            VueBlock::new(&fs::read_to_string(project_dir_info.app_vue())?)
                .update_template_block(&template_content)?
                .get_source(),
        )?;
    }

    Ok(())
}

fn find_module<'a>(
    npm_modules: &'a [NpmModuleInfo],
    package_name: &str,
) -> Option<&'a NpmModuleInfo> {
    npm_modules
        .iter()
        .find(|&module| module.package_name == package_name)
}

fn build_template(base_entry: &EntryInfo, slot_infos: &[SlotInfo]) -> String {
    let mut template = format!("<{}>\n", base_entry.name);

    for slot_info in slot_infos {
        template.push_str(&format!(
            "  <template v-slot=\"{}\">\n    <{} />\n  </template>\n",
            slot_info.slot_name, slot_info.element_name
        ));
    }

    template.push_str(&format!("</{}>", base_entry.name));

    template
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_build_template() {
        let entry_info = EntryInfo {
            package_name: "todolist_base".to_string(),
            name: "TodoListBase".to_string(),
        };
        let slot_infos = vec![
            SlotInfo {
                slot_name: "content".to_string(),
                element_name: "TodoList".to_string(),
            },
            SlotInfo {
                slot_name: "header".to_string(),
                element_name: "Header".to_string(),
            },
        ];

        let result = build_template(&entry_info, &slot_infos);
        let expected = r#"<TodoListBase>
  <template v-slot="content">
    <TodoList />
  </template>
  <template v-slot="header">
    <Header />
  </template>
</TodoListBase>"#;

        assert_eq!(result, expected);
    }
}
